package website

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"encoding/xml"
	"io/ioutil"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"golang.org/x/net/websocket"
)

type Result struct {
	Ok   bool        `json:"ok" xml:"ok"`
	Msg  string      `json:"msg" xml:"msg"`
	Data interface{} `json:"data" xml:"data"`
}

type Context struct {
	r       *http.Request
	w       http.ResponseWriter
	Session *Session
	v       map[string]interface{}
}

func NewContext(w http.ResponseWriter, r *http.Request, cfg *Config, manager *Manager) *Context {
	ctx := &Context{
		r:       r,
		w:       w,
		Session: RegSession(w, r, cfg.SessionTimeOut, cfg.EnableTLS, manager),
	}
	err := ctx.parseForm()
	if nil != err {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	return ctx
}

func (c *Context) SetContentType(t string) {
	c.w.Header().Set("Content-Type", t)
}

func (c *Context) OutHtml(tpl string, obj interface{}) {
	c.SetContentType("text/html; charset=UTF-8")
	rb := bytes.NewBufferString("")
	err := templates.ExecuteTemplate(rb, tpl, obj)
	if nil != err {
		http.Error(c.w, err.Error(), http.StatusInternalServerError)
		return
	} else {
		GzipHtmlPrint(rb, c.w, c.r)
		return
	}
}

func (c *Context) OutJson(obj interface{}) {
	c.SetContentType("application/json; charset=UTF-8")
	b, err := json.Marshal(obj)
	if nil != err {
		http.Error(c.w, err.Error(), http.StatusInternalServerError)
	} else {
		GzipBinaryPrint(b, c.w, c.r)
	}
	return
}

func (c *Context) OutXML(obj interface{}) {
	c.SetContentType("text/xml; charset=UTF-8")
	b, err := xml.Marshal(obj)
	if nil != err {
		http.Error(c.w, err.Error(), http.StatusInternalServerError)
	} else {
		GzipBinaryPrint(b, c.w, c.r)
	}
	return
}

func (c *Context) GetFormValue(key string) string {
	fv := c.v[key]
	if nil != fv {
		return strings.TrimSpace(ToStr(fv.([]string)[0]))
	} else {
		return ""
	}
}

func (c *Context) GetFormValues(v string) []string {
	fv := c.v[v]
	if nil != fv {
		return fv.([]string)
	} else {
		return nil
	}
}

func (c *Context) parseForm() error {
	err := c.r.ParseForm()
	if nil != err {
		return err
	}
	paramMap := make(map[string]interface{})
	s := c.r.Form
	for k, v := range s {
		if nil != paramMap[k] {
			paramArr := make([]interface{}, 0, 0)
			paramArr = append(paramArr, paramMap[k])
			paramArr = append(paramArr, v)
		} else {
			paramMap[k] = v
		}

	}
	c.v = paramMap
	return nil
}

func (c *Context) Redirect(url string) {
	http.Redirect(c.w, c.r, url, http.StatusMovedPermanently)
	return
}

func (c *Context) BasicAuth(s string) {
	c.w.Header().Set("WWW-Authenticate", "Basic realm="+s)
	c.w.WriteHeader(http.StatusUnauthorized)
	return
}

func (c *Context) GetJsonByte() []byte {
	ct := c.r.Header.Get("Content-Type")
	if strings.Contains(ct, "application/json") {
		b, err := ioutil.ReadAll(c.r.Body)
		if nil != err {
			http.Error(c.w, err.Error(), http.StatusInternalServerError)
			return nil
		} else {
			return b
		}
	} else {
		return nil
	}
}

func (c *Context) IsLogin() bool {
	if nil != c.Session.Get("is_login") && c.Session.Get("is_login").(bool) {
		return true
	} else {
		return false
	}
}

func (c *Context) GetSessionUser() interface{} {
	if c.IsLogin() {
		su := c.Session.Get("user_info")
		if nil != su {
			return su
		} else {
			return nil
		}
	} else {
		return nil
	}
}

func initAesKey() {
	uuid := make([]byte, 8)
	n, err := rand.Read(uuid)
	if n != len(uuid) || err != nil {
		panic(err)
	}
	uuid[4] = 0x80
	uuid[2] = 0x40
	aesKey = hex.EncodeToString(uuid)
}

func LoadMime(path string) error {
	file, err := os.Open(path)
	if nil != err {
		return err
	}
	defer file.Close()
	scan := bufio.NewScanner(file)
	for scan.Scan() {
		text := strings.TrimSpace(scan.Text())
		if !strings.EqualFold(text, "") && !strings.Contains(text, "{") && !strings.Contains(text, "}") {
			text = SignHandle(SignHandle(text, "#"), ";")
			fields := strings.Fields(text)
			if len(fields) <= 1 {
				continue
			}
			mimeType := fields[0]
			for _, ext := range fields[1:] {
				err := mime.AddExtensionType("."+ext, mimeType)
				if nil != err {
					break
				}
			}
		}
	}
	return nil
}

type tmplPath struct {
	name   string
	path   string
	suffix string
}

var (
	templatesFuncMap template.FuncMap
	templates        *template.Template
	tmplPaths        map[string]tmplPath
)

func templatePathWalk(p string, f os.FileInfo, suffix string, err error) error {
	if f == nil {
		return err
	} else if f.IsDir() {
		return nil
	} else if (f.Mode() & os.ModeSymlink) > 0 {
		return nil
	} else {
		if f.Size() > 0 {
			sx := filepath.Ext(p)
			if strings.EqualFold(strings.ToLower(sx), strings.ToLower(suffix)) {
				p1 := strings.TrimSuffix(p, sx)
				tmplPaths[p1] = tmplPath{path: p, name: f.Name(), suffix: sx}
			}
		}
	}
	return err
}

func InitTemplates(dirName string, suffix string) error {
	templatesFuncMap = make(template.FuncMap)
	tmplPaths = make(map[string]tmplPath)

	err := filepath.Walk(strings.TrimRight(dirName, "/"), func(p string, f os.FileInfo, err error) error {
		return templatePathWalk(p, f, suffix, err)
	})

	if nil != err {
		return err
	}

	for k, v := range tmplPaths {
		if strings.EqualFold(strings.ToLower(v.suffix), strings.ToLower(suffix)) {
			tk := k[len(dirName)+1 : len(k)]
			htmlStr, err := ioutil.ReadFile(v.path)
			if err != nil {
				return err
			}
			htmlTxt := string(htmlStr)
			if len(htmlTxt) != 0 {
				var t *template.Template
				if templates == nil {
					templates = template.New(tk).Funcs(templatesFuncMap)
				}
				if tk == templates.Name() {
					t = templates
				} else {
					t = templates.New(tk).Funcs(templatesFuncMap)
				}
				t = t.Delims("<abc%", "%>")
				_, err = t.Parse(htmlTxt)
				if nil != err {
					return err
				}
				templates = template.Must(templates, err)
			}
		}
	}
	return nil
}

func Run(cfg *Config) {
	InitLogger(cfg)
	initAesKey()

	LoadMime(cfg.MimePath)
	if err := InitTemplates(cfg.TemplatePath, cfg.TemplateSuffix); nil != err {
		logFatal(err)
	}

	static_handler := http.StripPrefix("/static/", http.FileServer(http.Dir(cfg.StaticDir)))
	console := &Console{cfg: cfg}
	mux := func(w http.ResponseWriter, r *http.Request) {

		if strings.HasPrefix(r.URL.Path, "/static/") {
			static_handler.ServeHTTP(w, r)
			return
		}

		ctx := NewContext(w, r, cfg, SessionManager)
		switch r.Method {
		case "GET", "POST", "PUT":
			if "/console/chksshdaddr" == r.URL.Path {
				console.ChkSSHSrvAddr(w, r, ctx)
				return
			}

			if strings.Contains(r.URL.Path, "/console/login/") {
				vm_addr := strings.TrimPrefix(r.URL.Path, "/console/login/")
				if "" != vm_addr {
					console.ConsoleLoginPage(w, r, vm_addr, ctx)
				} else if "POST" == r.Method {
					console.ConsoleLogin(w, r, ctx)
				}
				return
			} else if strings.Contains(r.URL.Path, "/console/login") && "POST" == r.Method {
				console.ConsoleLogin(w, r, ctx)
				return
			}

			if strings.Contains(r.URL.Path, "/console/logout/") || strings.Contains(r.URL.Path, "/console/logout") {
				console.ConsoleLogout(w, r, ctx)
				return
			}

			if strings.Contains(r.URL.Path, "/console/main/") {
				vm_info := strings.TrimPrefix(r.URL.Path, "/console/main/")
				if "" != vm_info {
					console.ConsoleMainPage(w, r, vm_info, ctx)
					return
				}
			}

			if strings.Contains(r.URL.Path, "/console/sshws/") {
				vm_info := strings.TrimPrefix(r.URL.Path, "/console/sshws/")
				if "" != vm_info {
					websocket.Handler(func(ws *websocket.Conn) {
						SSHWebSocketHandler(ws, vm_info, ctx)
					}).ServeHTTP(w, r)
					return
				}
			}

			if "GET" == r.Method && strings.Contains(r.URL.Path, "/pprof/") {
				PprofHandler(w, r, ctx)
				return
			}
		}

		console.ConsoleLoginPage(w, r, "", ctx)
	}

	//mux := NewServeMux(cfg)
	//mux.AddStaticDir(cfg.StaticDir)

	//mux.HandleFunc("get,post", "/", console.ConsoleLoginPage)
	//mux.HandleFunc("get,post", "/console/chksshdaddr", console.ChkSSHSrvAddr)
	//mux.HandleFunc("get,post", "/console/login/:vm_addr", console.ConsoleLoginPage)
	//mux.HandleFunc("post", "/console/login", console.ConsoleLogin)
	//mux.HandleFunc("get,post", "/console/logout", console.ConsoleLogout)
	// mux.HandleFunc("get,post", "/console/main/:vm_info", console.ConsoleMainPage)
	// mux.Handle("get", "/console/sshws/:vm_info")
	// mux.HandleFunc("get", "/pprof", func(w http.ResponseWriter, r *http.Request) {
	// 	PprofHandler(w, r, cfg)
	// })
	RunServer(cfg, http.HandlerFunc(mux))
}
